package com.hoho.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HoHoApplicationContext {

    Class clazz;

    // 存放beanDefinition的容器
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    // 存放单例Bean的容器
    private Map<String,Object> singletonMap = new HashMap<>();

    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public HoHoApplicationContext(Class clazz) {
        scan(clazz);
        this.clazz = clazz;

//        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
//            String beanName = entry.getKey();
//            BeanDefinition beanDefinition = entry.getValue();
//            if (beanDefinition.getScope().equals("singleton")) {
//
//                Object bean = createBean(beanName, beanDefinition);
//                singletonMap.put(beanName, bean);
//
//            }
//        }
    }

    /**
     * 包扫描
     * @param clazz
     */
    private void scan(Class clazz) {
        // 判断是否存在包扫描注解
        if(clazz.isAnnotationPresent(ComponentScan.class)){
            // 获取包扫描的注解类
            ComponentScan componentScan = (ComponentScan)clazz.getAnnotation(ComponentScan.class);
            // 获取所需要扫描包的路径
            String scanPackage = componentScan.value();
            // 正常Spring在这个环节用的是ASM技术，实现起来比较复杂，因此此处直接用当前类的加载器进行类加载
            scanPackage = scanPackage.replace(".", "/");
            URL resource = HoHoApplicationContext.class.getClassLoader().getResource(scanPackage);
            File directory = new File(resource.getFile());
            // 加载到了文件
            if(directory.isDirectory()){
                File[] files = directory.listFiles();
                // 对加载的文件进行循环处理
                for(File file : files){
                    try {
                        // 拼接类加载所需要的全类名
                        String fileName = file.getName().substring(0, file.getName().indexOf(".class"));
                        fileName = scanPackage + "/" + fileName;
                        fileName = fileName.replace("/",".");
                        // 进行类加载
                        Class<?> aClass = HoHoApplicationContext.class.getClassLoader().loadClass(fileName);
                        // 生成BeanDefinition
                        createBeanDefinition(aClass);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void createBeanDefinition(Class<?> aClass) throws Exception{
        // 判断是否存在Bean注解
        if(aClass.isAnnotationPresent(Component.class)){
            if (BeanPostProcessor.class.isAssignableFrom(aClass)) {
                BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
                beanPostProcessorList.add(instance);
            }

            // 获取Bean注解
            Component component = aClass.getAnnotation(Component.class);
            // 获取Bean名称
            String beanName = component.value();
            // 如果没有配置Bean名称，则取类名（首字母小写）作为beanName
            if(beanName.equals("")){
                beanName = Introspector.decapitalize(aClass.getSimpleName());
            }
            // 获取Bean的生命周期，默认单例（singleton）,这里只处理两种情况，单例及原型（prototype）
            String scope = "singleton";
            if(aClass.isAnnotationPresent(Scope.class)){
                Scope scopeAnno = aClass.getAnnotation(Scope.class);
                scope = scopeAnno.value();
            }
            // 创建bd，并设置属性
            BeanDefinition bd = new BeanDefinition();
            bd.setClazz(aClass);
            bd.setScope(scope);
            // 懒加载这里就不做展开了
            bd.setLazy(true);
            // 将bd放入到容器中
            beanDefinitionMap.put(beanName,bd);
        }
    }

    public Object getBean(String beanName){
        // 首先判断是否存在bd
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new RuntimeException("Bean Is Not Exist!");
        }
        // 获取Bean定义
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        // 判断是否单例
        if(beanDefinition.getScope().equals("singleton")){
            // 如果为单例，首先从单例池中取出bean
            Object singletonBean = singletonMap.get(beanName);
            if(singletonBean == null){
                // 不存在则创建bean
                singletonBean = createBean(beanName,beanDefinition);
                // 将bean添加到单例池
                singletonMap.put(beanName,singletonBean);
            }
            return singletonBean;
        } else {
            // 创建非单例bean
            Object prototypeBean = createBean(beanName,beanDefinition);
            return prototypeBean;
        }
    }

    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getClazz();
        Object instance = null;
        try {
            // 实例化
            instance = clazz.getConstructor().newInstance();
            // 依赖注入
            inject(instance,clazz);
            // aware回调
            doAware(instance,beanName);
            // 初始化前
            beanPostProcessorBeforeInitialization(instance,beanName);
            // 初始化
            beanPostProcesssor(instance);
            // 初始化后
            beanPostProcessorAfterInitialization(instance,beanName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    private void beanPostProcessorAfterInitialization(Object instance,String beanName) {
        for(BeanPostProcessor beanPostProcessor : beanPostProcessorList){
            beanPostProcessor.postProcessAfterInitialization(instance,beanName);
        }

    }

    /**
     * 初始化
     * @param instance
     */
    private void beanPostProcesssor(Object instance) {
        if(instance instanceof InitializingBean){
            ((InitializingBean) instance).afterPropertiesSet();
        }
    }

    /**
     * 初始化前
     * @param instance
     * @param beanName
     */
    private void beanPostProcessorBeforeInitialization(Object instance,String beanName) {
        for(BeanPostProcessor beanPostProcessor : beanPostProcessorList){
            beanPostProcessor.postProcessBeforeInitialization(instance,beanName);
        }
    }

    private void doAware(Object instance,String beanName) {
        if(instance instanceof BeanNameAware){
            ((BeanNameAware) instance).setBeanName(beanName);
        }
    }

    private void inject(Object instance, Class clazz) {
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field field : declaredFields){
            if(field.isAnnotationPresent(Autowired.class)){
                field.setAccessible(true);
                try {
                    field.set(instance,getBean(field.getName()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
